import {
    Animation,
    AnimationClip,
    Prefab,
    resources,
    AnimationState,
    director,
    find,
    isValid,
    Node,
    ParticleSystem,
    Vec3
} from 'cc';
import {ResourceUtils} from "db://assets/scripts/common/ResourceUtils";
import {PoolManager} from "db://assets/scripts/common/PoolManager";
import {SkillWarningType} from "db://assets/scripts/data/model/SkillWarningType";
import {RewardController} from "db://assets/scripts/game/RewardController";
import {WarningCircleEffect} from "db://assets/scripts/game/effect/warningSkill/WarningCircleEffect";
import {WarningLineEffect} from "db://assets/scripts/game/effect/warningSkill/WarningLineEffect";
import {WarningStripEffect} from "db://assets/scripts/game/effect/warningSkill/WarningStripEffect";

/**
 * 特效管理器
 */
export class EffectManager {
    /**
     * 父节点
     */
    private parent: Node;

    /**
     * 特效管理器实例
     */
    private static instance: EffectManager;

    /**
     * 获取特效管理器实例
     */
    public static getInstance() {
        if (this.instance) {
            return this.instance;
        }
        this.instance = new EffectManager();
        return this.instance;
    }

    /**
     * 获取特效父节点
     */
    public getParent(): Node {
        // 当特效父节点不存在时
        if (!this.parent) {
            // 查找特效父节点
            let effectParent = find("effectManager");
            // 当找到特效父节点时
            if (effectParent) {
                // 记录父节点
                this.parent = effectParent;
            } else {
                // 当未找到特效父节点时，重新创建一个
                this.parent = new Node("effectManager");
                // 将父节点添加至场景中
                director.getScene().addChild(this.parent);
            }
        }
        return this.parent;
    }

    /**
     * 重置特效节点状态
     * @param effectNode 特效节点
     * @param animation 动画名字
     */
    public resetEffectState(effectNode: Node, animation?: string) {
        // 当特效节点无效时
        if (!isValid(effectNode)) {
            return;
        }

        // 获取粒子列表
        let particles: ParticleSystem[] = effectNode.getComponentsInChildren(ParticleSystem);

        // 当粒子列表不为空时
        if (particles.length) {
            // 遍历列子列表
            for (let particle of particles) {
                particle.stop();
                particle.clear();
            }
        }

        // 获取动画列表
        let animations: Animation[] = effectNode.getComponentsInChildren(Animation);

        // 当动画列表不为空时
        if (animations.length) {
            // 遍历动画列表
            for (let item of animations) {
                // 当存在默认动画切片时
                if (item.defaultClip && item.defaultClip.name) {
                    // 动画状态
                    let animationState: AnimationState;

                    if (animation) {
                        animationState = item.getState(animation);
                    }

                    if (!animationState) {
                        animationState = item.getState(item.defaultClip.name);
                    }

                    if (animationState) {
                        animationState.stop();
                        animationState.time = 0;
                        animationState.sample();
                    }
                }
            }
        }
    }

    /**
     * 加载特效节点并播放节点下面的动画、粒子
     *
     * @param {boolean} [isLocal=true] 是否将特效节点设置在本地坐标或者世界坐标下
     * @param {Node} target 特效所在节点
     * @param {string} effectPath 特效路径
     * @param {number} [scale=1] 缩放大小
     * @param {(Vec3 | null)} pos 坐标
     * @param {(Vec3 | null)} eulerAngles 角度
     * @param {boolean} [isPlayAnimation=true] 是否播放动画
     * @param {boolean} [isPlayParticle=true] 是否播放特效
     * @param {number} [speed=1] 播放速度
     * @param {boolean} [isRecycle=false] 是否回收
     * @param {number} [recycleTime=0] 回收时间
     * @param {(Function | null)} callback 回调函数
     * @returns
     * @memberof EffectManager
     */
    public loadAndPlayEffect(isLocal: boolean = true,
                             target: Node,
                             effectPath: string,
                             scale: number = 1,
                             pos?: Vec3,
                             eulerAngles?: Vec3,
                             isPlayAnimation: boolean = true,
                             isPlayParticle: boolean = true,
                             speed: number = 1,
                             isRecycle: boolean = false,
                             recycleTime?: number | null,
                             callback?: Function | null) {

        //如果是本地坐标，父节点被回收的时候不播放
        if (isLocal && (!target || !target.parent)) {
            return;
        }

        ResourceUtils.loadEffect(effectPath).then((prefab: any) => {
            let parentNode: Node = isLocal ? target : this.parent;
            let effectNode: Node = PoolManager.getInstance().getNode(prefab, parentNode);

            if (isLocal) {
                effectNode.setScale(scale, scale, scale);

                if (pos) {
                    effectNode.setPosition(pos);
                }

                if (eulerAngles) {
                    effectNode.eulerAngles = eulerAngles;
                }
            } else {
                effectNode.setWorldScale(scale, scale, scale);

                if (pos) {
                    effectNode.setWorldPosition(pos);
                }

                if (eulerAngles) {
                    effectNode.setWorldRotationFromEuler(eulerAngles.x, eulerAngles.y, eulerAngles.z);
                }
            }

            this.playEffect(effectNode, isPlayAnimation, isPlayParticle, speed, isRecycle, recycleTime, callback);
        })
    }

    /**
     * 播放节点下面的动画、粒子
     *
     * @param {Node} ndEffect 特效节点
     * @param {boolean} [isPlayAnimation=true] 是否播放动画
     * @param {boolean} [isPlayParticle=true] 是否播放特效
     * @param {number} [speed=1] 播放速度
     * @param {boolean} [isRecycle=false] 是否回收
     * @param {number} [recycleTime=0] 回收时间
     * @param {(Function | null)} callback 回调函数
     * @returns
     * @memberof EffectManager
     */
    public playEffect(ndEffect: Node, isPlayAnimation: boolean = true, isPlayParticle: boolean = true, speed: number = 1, isRecycle: boolean = false, recycleTime?: number | null, callback?: Function | null) {
        //特效最长持续时间
        let maxDuration: number = 0;

        if (isPlayAnimation) {
            let duration = this.playAnimation(ndEffect, speed, null, false, false, null, null);
            maxDuration = duration > maxDuration ? duration : maxDuration;

        }

        if (isPlayParticle) {
            let duration = this.playParticle(ndEffect, speed, false, null, null);
            maxDuration = duration > maxDuration ? duration : maxDuration;
        }

        maxDuration = recycleTime && recycleTime > 0 ? recycleTime : maxDuration;

        if (callback || isRecycle) {
            setTimeout(() => {
                if (ndEffect.parent) {
                    callback && callback();

                    if (isRecycle) {
                        PoolManager.getInstance().putNode(ndEffect);
                    } else {
                        ndEffect.destroy();
                    }
                }
            }, maxDuration * 1000)
        }
    }

    /**
     * 播放节点上的默认动画特效
     *
     * @param {Node} ndEffect 特效节点
     * @param {number} [speed=1] 动画播放速度
     * @param {(string | null)} animationName 动画名称（当节点下只有一个动画组件，并指定播放动画的时候才会使用这个参数，否则都使用默认动画）
     * @param {boolean} [isLoop=false] 是否循环播放
     * @param {boolean} [isRecycle=false] 是否回收
     * @param {(number | null)} recycleTime 回收时间,如果为null则使用maxDuration
     * @param {(Function | null)} callback 回调函数
     * @returns
     * @memberof EffectManager
     */
    public playAnimation(ndEffect: Node, speed: number = 1, animationName: string | null, isLoop: boolean = false, isRecycle: boolean = false, recycleTime?: number | null, callback?: Function | null) {
        //动画播放最长时间
        let maxDuration: number = 0;
        let aniState: AnimationState = null!;

        if (!ndEffect.active) {
            ndEffect.active = true;
        }

        let arrAni: Animation[] = ndEffect.getComponentsInChildren(Animation);

        if (arrAni.length) {
            arrAni.forEach((element: Animation, idx: number) => {
                let aniName = animationName ? animationName : element?.defaultClip?.name;

                if (aniName) {
                    aniState = element.getState(aniName);
                    if (aniState) {
                        aniState.time = 0;
                        aniState.speed = speed;
                        aniState.sample();

                        let duration = aniState.duration;
                        maxDuration = duration > maxDuration ? duration : maxDuration;

                        if (isLoop) {
                            aniState.wrapMode = AnimationClip.WrapMode.Loop;
                        } else {
                            aniState.wrapMode = AnimationClip.WrapMode.Normal;
                        }

                        element?.play(aniName);
                    }
                }
            })

            maxDuration = recycleTime && recycleTime > 0 ? recycleTime : maxDuration;

            let cb = () => {
                if (ndEffect && ndEffect.parent) {
                    callback && callback();

                    if (isRecycle) {
                        PoolManager.getInstance().putNode(ndEffect);
                    }
                }
            }

            if (callback || isRecycle) {
                if (arrAni.length === 1) {
                    arrAni[0].once(Animation.EventType.FINISHED, () => {
                        cb();
                    })
                } else {
                    setTimeout(() => {
                        cb();
                    }, maxDuration * 1000)
                }
            }

            return maxDuration;
        } else {
            console.warn(`###${ndEffect.name}节点下没有动画特效`);
            return 0;
        }
    }

    /**
     * 播放节点上的粒子特效
     *
     * @param {Node} effectNode 特效节点
     * @param {number} [speed=1] 粒子播放速度
     * @param {boolean} [isRecycle=false] 是否需要回收特效节点
     * @param {(number | null)} [recycleTime] 回收时间, 如果为null则使用maxDuration
     * @param {(Function | null)} [callback] 回调函数
     * @returns 返回播放完成所需秒数
     * @memberof EffectManager
     */
    public playParticle(effectNode: Node, speed: number = 1, isRecycle: boolean = false, recycleTime?: number | null, callback?: Function | null) {
        //粒子播放最长时间
        let maxDuration: number = 0;

        if (!effectNode.active) {
            effectNode.active = true;
        }

        let particles: ParticleSystem[] = effectNode.getComponentsInChildren(ParticleSystem);

        if (particles.length) {
            for (let particle of particles) {
                particle.simulationSpeed = speed;
                particle.clear();
                particle.stop();
                particle.play();

                let duration: number = particle.duration;
                maxDuration = duration > maxDuration ? duration : maxDuration;
            }

            //使用传进来的回收时间，否则设置为时长最长
            maxDuration = recycleTime && recycleTime > 0 ? recycleTime : maxDuration;

            if (callback || isRecycle) {
                setTimeout(() => {
                    if (effectNode && effectNode.parent) {
                        callback && callback();

                        if (isRecycle) {
                            PoolManager.getInstance().putNode(effectNode);
                        }
                    }
                }, maxDuration * 1000)
            }

            return maxDuration;
        } else {
            console.warn(`###${effectNode.name}节点下没有粒子特效`);
            return 0;
        }
    }

    /**
     * 展示奖励(金币、爱心)弹跳
     *
     * @param monsterNode 敌人
     * @param modelPath 模型路径
     * @param rewardNumber 奖励数字
     * @param callback 回调
     */
    public showRewardBounce(monsterNode: Node, modelPath: string, rewardNumber: number = 1, callback?: Function) {
        let time = rewardNumber <= 10 ? 0.15 : 0.07;
        ResourceUtils.loadModel(modelPath).then((pf: any) => {
            for (let i = 0; i < rewardNumber; i++) {
                let ndReward = PoolManager.getInstance().getNode(pf, this.parent);
                ndReward.setWorldPosition(monsterNode.worldPosition.x, 1.65, monsterNode.worldPosition.z);
                ndReward.active = false;
                let rewardController = ndReward.getComponent(RewardController);
                rewardController.init((i + 1) * time, this.parent);
            }

            callback && callback();
        });
    }

    /**
     * 展示预警
     *
     * @param {string} warningType
     * @param {number} scale
     * @param {*} scriptParent
     * @param callback
     * @memberof EffectManager
     */
    public showWarning(warningType: SkillWarningType, scale: number, scriptParent: any, callback?: Function) {
        resources.load(`warning/${warningType.toString()}`, Prefab, (err: Error, prefab: Prefab) => {
            if (err) {
                console.log(err);
                return;
            }

            let ndWarning = PoolManager.getInstance().getNode(prefab, this.parent);

            let scriptWarning: any = null;
            switch (warningType) {
                case SkillWarningType.WARNING_LINE:
                    scriptWarning = ndWarning.getComponent(WarningLineEffect);
                    break;
                case SkillWarningType.WARNING_STRIP:
                    scriptWarning = ndWarning.getComponent(WarningStripEffect);
                    break;
                case SkillWarningType.WARNING_CIRCLE:
                    scriptWarning = ndWarning.getComponent(WarningCircleEffect);
                    break;
            }

            scriptWarning?.init(scale, scriptParent);

            scriptParent.scriptWarning = scriptWarning;

            callback && callback();
        });
    }

    /**
     * 展示闪电特效连接
     *
     * @param {Node} parent
     * @param {Node} target
     * @memberof EffectManager
     */
    public showLightningChain(parent: Node, target: Node) {
        ResourceUtils.loadEffect(`lightningChain/lightningChain`).then((pf: any) => {
            let ndEffect = PoolManager.getInstance().getNode(pf, parent) as Node;
            ndEffect.setWorldPosition(parent.worldPosition.x, 2.3, parent.worldPosition.z);

            let offsetPos: Vec3 = new Vec3();

            Vec3.subtract(offsetPos, target.worldPosition, parent.worldPosition);
            ndEffect.setWorldScale(1, offsetPos.length(), 1);
            ndEffect.forward = offsetPos.normalize().negative();

            setTimeout(() => {
                PoolManager.getInstance().putNode(ndEffect);
            }, 100)
        });
    }
}